using AuthServer.Data;
using AuthServer.Helpers;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Abstractions;
using OpenIddict.Server.AspNetCore;
using Polly;
using System.Globalization;
using System.Security.Claims;
using System.Text.Json.Serialization;
using System.Text.Json;
using static OpenIddict.Abstractions.OpenIddictConstants;
namespace AuthServer.Controllers
{
    [ApiController]
    public class AuthorizationController : ControllerBase
    {

        private readonly IOpenIddictScopeManager _scopeManager;
        public AuthorizationController(
            IOpenIddictScopeManager scopeManager
            )
        {
            _scopeManager = scopeManager;

        }

        [HttpGet("/authorize")]
        public async Task<IResult> Accept()
        {
            // Retrieve the OpenIddict server request from the HTTP context.
            var request = HttpContext.GetOpenIddictServerRequest();

            // Create the claims-based identity that will be used by OpenIddict to generate tokens.
            var identity = new ClaimsIdentity(
                authenticationType: TokenValidationParameters.DefaultAuthenticationType,
                nameType: Claims.Name,
                roleType: Claims.Role);

            UserInfo userInfo = new UserInfo();
            userInfo.UserName = request?.Username ?? string.Empty;
            userInfo.UserRole = request?["userRole"].ToString() ?? string.Empty;
            userInfo.UserId = request?["userId"] == null ? 0 : int.Parse(request?["userId"].ToString());
            userInfo.DingId = request?["dingId"].ToString() ?? string.Empty;
            // Add the claims that will be persisted in the tokens.
            identity.AddClaim(new Claim(Claims.Subject, userInfo?.UserId?.ToString(CultureInfo.InvariantCulture) ?? ""));
            identity.AddClaim(new Claim("UserName", userInfo?.UserName ?? ""));
            identity.AddClaim(new Claim("UserRole", userInfo?.UserRole ?? ""));
            identity.AddClaim(new Claim("UserId", userInfo?.UserId.Value.ToString() ?? ""));
            identity.AddClaim(new Claim(Claims.PreferredUsername, userInfo?.UserName ?? ""));
            identity.SetScopes(request.GetScopes());

            identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync());

            // Allow all claims to be added in the access tokens.
            identity.SetDestinations(claim => new[] { Destinations.AccessToken });

            return Results.SignIn(new ClaimsPrincipal(identity), properties: null, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
        }

        [Authorize, FormValueRequired("submit.Deny")]
        [HttpPost("authorize")]
        // Notify OpenIddict that the authorization grant has been denied by the resource owner
        // to redirect the user agent to the client application using the appropriate response_mode.
        public IActionResult Deny() => Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);

        [HttpGet("logout")]
        public IActionResult Logout() => Content("");

        [ActionName(nameof(Logout)), HttpPost("/logout"), ValidateAntiForgeryToken]
        public async Task<IActionResult> LogoutPost()
        {
            // Ask ASP.NET Core Identity to delete the local and external cookies created
            // when the user agent is redirected from the external identity provider
            // after a successful authentication flow (e.g Google or Facebook).
            //await _signInManager.SignOutAsync();

            // Returning a SignOutResult will ask OpenIddict to redirect the user agent
            // to the post_logout_redirect_uri specified by the client application or to
            // the RedirectUri specified in the authentication properties if none was set.
            return SignOut(
                authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
                properties: new AuthenticationProperties
                {
                    RedirectUri = "/"
                });
        }

        [HttpPost("token")]
        public async Task<IActionResult> Exchange()
        {
            var requestres = HttpContext.GetOpenIddictServerRequest() ??
                throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");

            if (requestres.IsAuthorizationCodeGrantType() || requestres.IsRefreshTokenGrantType())
            {
                // Retrieve the claims principal stored in the authorization code/refresh token.
                var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
                var request = HttpContext.GetOpenIddictServerRequest();
                UserInfo userInfo = new UserInfo();
                userInfo.UserName = request?.Username ?? string.Empty;
                userInfo.UserRole = request?["userRole"].ToString() ?? string.Empty;
                userInfo.UserId = request?["userId"] == null ? 0 : int.Parse(request?["userId"].ToString());
                userInfo.DingId = request?["dingId"].ToString() ?? string.Empty;
                if (userInfo.UserId == 0 || userInfo.UserName == "")
                {
                    return Forbid(
                        authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
                        properties: new AuthenticationProperties(new Dictionary<string, string>
                        {
                            [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
                            [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The token is no longer valid."
                        }));
                }
                // Create the claims-based identity that will be used by OpenIddict to generate tokens.
                var identity = new ClaimsIdentity(
                    authenticationType: TokenValidationParameters.DefaultAuthenticationType,
                    nameType: Claims.Name,
                    roleType: Claims.Role);

                // Add the claims that will be persisted in the tokens.
                identity.AddClaim(new Claim(Claims.Subject, userInfo?.UserId?.ToString(CultureInfo.InvariantCulture) ?? ""));
                identity.AddClaim(new Claim("UserName", userInfo?.UserName ?? ""));
                identity.AddClaim(new Claim("UserRole", userInfo?.UserRole ?? ""));
                identity.AddClaim(new Claim("UserId", userInfo?.UserId.Value.ToString() ?? ""));
                identity.SetScopes(request?.GetScopes());
                identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync());

                // Allow all claims to be added in the access tokens.
                identity.SetDestinations(claim => new[] { Destinations.AccessToken });
                return SignIn(new ClaimsPrincipal(identity), properties: null!, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
            }

            throw new InvalidOperationException("The specified grant type is not supported.");
        }

        private static IEnumerable<string> GetDestinations(Claim claim)
        {
            // Note: by default, claims are NOT automatically included in the access and identity tokens.
            // To allow OpenIddict to serialize them, you must attach them a destination, that specifies
            // whether they should be included in access tokens, in identity tokens or in both.

            switch (claim.Type)
            {
                case Claims.Name or Claims.PreferredUsername:
                    yield return Destinations.AccessToken;

                    if (claim.Subject.HasScope(Scopes.Profile))
                        yield return Destinations.IdentityToken;

                    yield break;

                case Claims.Email:
                    yield return Destinations.AccessToken;

                    if (claim.Subject.HasScope(Scopes.Email))
                        yield return Destinations.IdentityToken;

                    yield break;

                case Claims.Role:
                    yield return Destinations.AccessToken;

                    if (claim.Subject.HasScope(Scopes.Roles))
                        yield return Destinations.IdentityToken;

                    yield break;

                // Never include the security stamp in the access and identity tokens, as it's a secret value.
                case "AspNet.Identity.SecurityStamp": yield break;

                default:
                    yield return Destinations.AccessToken;
                    yield break;
            }
        }
    }
}

